/*
 *
 *  *    Copyright © OpenAtom Foundation.
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *         http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.inspur.edp.commonmodel.engine.api.data;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.inspur.edp.bef.bizentity.GspBizEntityObject;
import com.inspur.edp.bef.bizentity.GspBusinessEntity;
import com.inspur.edp.cef.designtime.api.IGspCommonField;
import com.inspur.edp.cef.designtime.api.element.GspAssociation;
import com.inspur.edp.cef.designtime.api.element.GspElementObjectType;
import com.inspur.edp.cef.designtime.api.util.MetadataUtil;
import com.inspur.edp.cef.entity.entity.AssoInfoBase;
import com.inspur.edp.cef.entity.entity.ICefData;
import com.inspur.edp.commonmodel.engine.api.exception.CMEngineException;
import com.inspur.edp.commonmodel.engine.api.exception.ErrorCodes;
import com.inspur.edp.das.commonmodel.IGspCommonElement;
import com.inspur.edp.das.commonmodel.IGspCommonModel;
import com.inspur.edp.lcm.metadata.api.entity.GspMetadata;
import lombok.var;

import java.util.*;
import java.util.stream.Collectors;

@JsonSerialize(using = AssoInfoJsonSerializer.class)
public class AssociationInfo extends AssoInfoBase implements IAssociationInfo, Cloneable {

    private HashMap<String, Object> values;
    //关联信息集合
    private List<GspAssociation> associations;
    //依赖模型集合
    private List<IGspCommonModel> refModels;
    //是否多关联
    private boolean isMultiAssociation;

    private final Object obj = new Object();

    public AssociationInfo(GspAssociation associationInfo) {
        this(Collections.singletonList(associationInfo));
    }

    public AssociationInfo(List<GspAssociation> associationInfos) {
        Objects.requireNonNull(associationInfos, "associations");
        if (associationInfos.isEmpty()) {
            throw new IllegalArgumentException("associations cannot be empty");
        }
        //检查列表中的每个元素是否为null
        for (int i = 0; i < associationInfos.size(); i++) {
            if (associationInfos.get(i) == null) {
                throw new IllegalArgumentException("association at index " + i + " cannot be null");
            }
        }
        initAssociations(associationInfos);
    }

    @Deprecated
    public AssociationInfo() {
    }

    private void initAssociations(List<GspAssociation> associationInfos) {
        GspAssociation firstAssociation = associationInfos.get(0);
        //初始化是否多关联
        this.isMultiAssociation = checkMultiAssociation(firstAssociation);
        //初始化关联信息
        this.associations = new ArrayList<>();
        if (this.isMultiAssociation) {
            this.associations = associationInfos;
        } else {
            this.associations.add(firstAssociation);
        }
        //初始化依赖模型
        initRefModels();
    }

    /**
     * 检查关联关系是否为多关联
     *
     * @param association 关联信息
     * @return 如果关联关系的所属元素是多重关联则返回true，否则返回false
     */
    private  boolean checkMultiAssociation(GspAssociation association) {
        return association.getBelongElement().isMultiAssociation();
    }

    /**
     * 设置单关联信息
     *
     * @param associationInfo 关联信息对象，不能为空
     * @throws IllegalArgumentException 当associationInfo为空、belongElement为空或存在多关联时抛出
     */
    @Deprecated
    public void setAssociation(GspAssociation associationInfo) {
        Objects.requireNonNull(associationInfo, "association");
        Objects.requireNonNull(associationInfo.getBelongElement(), "belongElement");
        // 多关联暂不支持
        if (checkMultiAssociation(associationInfo)) {
            throw new IllegalArgumentException("multiAssociation not support");
        }
        // 添加新的关联信息
        this.associations = new ArrayList<>();
        this.associations.add(associationInfo);
        initRefModels();
    }

    public final GspAssociation getAssociation() {
        if (associations == null || associations.isEmpty()) {
            return null;
        }
        return associations.get(0);
    }

    public final List<GspAssociation> getAssociations() {
        return associations;
    }

    /**
     * 初始化关联模型列表
     * 该方法用于初始化refModels列表，遍历associations集合，
     * 根据每个关联信息获取对应的元数据和模型对象，并添加到refModels列表中
     */
    private void initRefModels() {
        refModels = new ArrayList<>();
        if (associations == null) {
            return;
        }
        for (GspAssociation associationInfo : associations) {
            GspMetadata refMetadata = MetadataUtil.getCustomRTMetadata(associationInfo.getRefModelID());
            Objects.requireNonNull(refMetadata, associationInfo.getBelongElement().getLabelID() + "associated metadata:" + associationInfo.getRefModelID());
            IGspCommonModel refModel = (IGspCommonModel) refMetadata.getContent();
            Objects.requireNonNull(refModel, "be");
            refModels.add(refModel);
        }
    }

    public Map<String, Object> getValues() {
        if (values == null) {
            int initialCapacity = 16; // 默认初始容量
            if (associations != null) {
                //计算所有 associations 的 getRefElementCollection().size() 总和 + 1
                initialCapacity = associations.stream()
                        .filter(Objects::nonNull)
                        .mapToInt(associationInfo -> associationInfo.getRefElementCollection() != null ? associationInfo.getRefElementCollection().size() : 0)
                        .sum() + 1;
            }
            values = new HashMap<>(initialCapacity);
        }
        return values;
    }

    @Override
    protected Object innerGetValue(String labelId) {
        Objects.requireNonNull(labelId, "labelId");
        checkRefElementExists(labelId);
        return getValues().get(labelId);
    }

    private volatile HashSet<String> refs;

    /**
     * 检查引用元素是否存在
     * VO变更集转BE变更集的时候，解析AssociationInfo 用clone的方式错误赋值了refs(vo上带出字段少)
     *
     * @param labelId 标签
     */
    private void checkRefElementExists(String labelId) {
        if (associations == null || associations.isEmpty()) {
            return;
        }

        // 检查是否是任何一个关联的belongElement
        for (GspAssociation assoc : associations) {
            if (assoc.getBelongElement() != null && labelId.equals(assoc.getBelongElement().getLabelID())) {
                return;
            }
        }

        if (refs == null) {
            synchronized (obj) {
                if (refs == null) {
                    // 收集所有关联中的引用元素
                    refs = new HashSet<>();
                    for (GspAssociation assoc : associations) {
                        if (assoc.getRefElementCollection() != null) {
                            for (IGspCommonField refElement : assoc.getRefElementCollection()) {
                                refs.add(refElement.getLabelID());
                            }
                        }
                    }
                }
            }
        }

        if (!refs.contains(labelId)) {
            throw new CMEngineException(ErrorCodes.CM_ENGINE_0001,
                    associations.get(0).getBelongElement().getLabelID(), labelId);
        }
    }


    @Override
    public ICefData copySelf() {
        return this;
    }

    @Override
    public ICefData copy() {
        return this;
    }

    @Override
    public List<String> getPropertyNames() {
        List<String> rez = new ArrayList<>();
        if (associations == null || associations.isEmpty()) {
            return rez;
        }
        for (GspAssociation association : associations) {
            if (association.getRefElementCollection() != null) {
                for (IGspCommonField refElement : association.getRefElementCollection()) {
                    rez.add(refElement.getLabelID());
                }
            }
        }
        rez.add(associations.get(0).getBelongElement().getLabelID());
        return rez;
    }

    @Override
    protected void innerSetValue(String labelId, Object value) {
        Objects.requireNonNull(labelId, "labelId");
        checkRefElementExists(labelId);
        getValues().put(labelId, value);
    }

    @Override
    public String getValue() {
        if (associations == null || associations.isEmpty()) {
            throw new CMEngineException(ErrorCodes.CM_ENGINE_0002);
        }
        // 返回belongElement的值
        return (String) getValue(associations.get(0).getBelongElement().getLabelID());
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        AssociationInfo result = (AssociationInfo) super.clone();
        //存在VO上带出字段和BE不一致的情况,需要进行重置
        result.refs = null;
        if (values != null) {
            result.values = cloneForValues(values);
        }
        return result;
    }

    //TODO
    private static <TKey, TValue> HashMap<TKey, TValue> cloneForValues(HashMap<TKey, TValue> source)
            throws CloneNotSupportedException {
        if (source == null) {
            return null;
        }
        HashMap<TKey, TValue> result = new HashMap<>(source.size(), 1);
        for (Map.Entry<TKey, TValue> pair : source.entrySet()) {
            Object value =
                    pair.getValue() != null && pair.getValue() instanceof AssociationInfo
                            ? ((AssociationInfo) pair.getValue()).clone()
                            : pair.getValue();
            result.put(pair.getKey(), (TValue) value);
        }
        return result;
    }

    @Deprecated
    public static AssociationInfo createData(GspAssociation gspAsso) {
        return new AssociationInfo(gspAsso);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof AssociationInfo)) {
            return false;
        }
        AssociationInfo other = (AssociationInfo) obj;
        if (isEmptyValues(values)) {
            return isEmptyValues(other.values);
        }
        if (isEmptyValues(other.values)) {
            return false;
        }
        Map<String, Object> otherValues = new HashMap<>(other.values);
        for (Map.Entry<String, Object> pair : values.entrySet()) {
            if (!valueEquals(pair.getValue(), otherValues.remove(pair.getKey()))) {
                return false;
            }
        }
        return isEmptyValues(otherValues);
    }

    private static boolean isEmptyValues(Map<String, Object> values) {
        return values == null
                || values.isEmpty()
                || values.values().stream().allMatch(item -> item == null);
    }

    private static boolean valueEquals(Object value1, Object value2) {
        if (value1 != null && value1 instanceof byte[] && value2 != null && value2 instanceof byte[]) {
            return Arrays.equals((byte[]) value1, (byte[]) value2);
        }
        if (value1 == null) {
            return value2 == null;
        }
        return value1.equals(value2);
    }

    @Override
    public Object innerCreateValue(String propName) {
        if(isMultiAssociation) {
            throw new IllegalArgumentException("multiAssociation not support");
        }
        GspAssociation association = associations.get(0);
        IGspCommonModel refModel = refModels.get(0);
        for (var refElement : association.getRefElementCollection()) {
            if (!refElement.getLabelID().equals(propName))
                continue;
            if (refElement.getObjectType() != GspElementObjectType.Association)
                continue;
            if (refElement.getIsRefElement()) {
                IGspCommonElement refBEElement = refModel.findElementById(refElement.getRefElementId());
                GspMetadata metadata = MetadataUtil.getCustomRTMetadata(refElement.getParentAssociation().getRefModelID());
                GspBusinessEntity gspBusinessEntity = (GspBusinessEntity) metadata.getContent();
                GspBizEntityObject gspBizEntityObject = gspBusinessEntity.getNode(refElement.getParentAssociation().getRefObjectCode());
                for (IGspCommonField gspCommonField : gspBizEntityObject.getContainElements()) {
                    if (gspCommonField.getID().equals(refElement.getRefElementId())) {
                        return createAssociationInfo(gspCommonField.getChildAssociations().get(0));
                    }
                }
            }
            return createAssociationInfo(association);
        }
        return null;
    }

    protected AssociationInfo createAssociationInfo(GspAssociation association) {
        return new AssociationInfo(association);
    }
}
